📋 CanvasChain 第1周 - 基础架构搭建详细设计文档
📊 1. 项目架构总览
🗂️ 2. Maven 多模块项目结构设计
canvas-chain/
│
├── canvas-chain-common/ # 【模块1】通用模块
│ ├── pom.xml
│ ├── src/main/java/
│ │ ├── com/canvas/chain/
│ │ │ ├── entity/ # ✅ 核心实体类
│ │ │ │ ├── User.java
│ │ │ │ ├── Artwork.java
│ │ │ │ ├── Auction.java
│ │ │ │ ├── Transaction.java
│ │ │ │ ├── Vote.java
│ │ │ │ ├── Wallet.java
│ │ │ │ ├── BlindBox.java
│ │ │ │ └── NFT.java
│ │ │ │
│ │ │ ├── dto/ # ✅ 数据传输对象
│ │ │ │ ├── request/
│ │ │ │ │ ├── LoginRequest.java
│ │ │ │ │ ├── RegisterRequest.java
│ │ │ │ │ └── ...
│ │ │ │ └── response/
│ │ │ │ ├── LoginResponse.java
│ │ │ │ ├── UserResponse.java
│ │ │ │ └── ...
│ │ │ │
│ │ │ ├── enums/ # ✅ 枚举类
│ │ │ │ ├── UserRole.java
│ │ │ │ ├── AuctionStatus.java
│ │ │ │ ├── TransactionStatus.java
│ │ │ │ └── ...
│ │ │ │
│ │ │ ├── util/ # ✅ 工具类
│ │ │ │ ├── IdGenerator.java # ID 生成
│ │ │ │ ├── JwtUtil.java # JWT 工具
│ │ │ │ ├── EncryptUtil.java # 加密工具
│ │ │ │ └── DateUtil.java
│ │ │ │
│ │ │ ├── exception/ # ✅ 异常定义
│ │ │ │ ├── BusinessException.java
│ │ │ │ ├── AuthException.java
│ │ │ │ └── ...
│ │ │ │
│ │ │ ├── config/ # ✅ 公共配置
│ │ │ │ ├── JacksonConfig.java
│ │ │ │ └── RestTemplateConfig.java
│ │ │ │
│ │ │ └── constant/ # ✅ 常量定义
│ │ │ ├── SystemConstant.java
│ │ │ └── ErrorCode.java
│ │ │
│ │ └── resources/
│ │ └── application-common.yml # 通用配置
│ │
│ └── pom.xml
│
├── canvas-chain-gateway/ # 【模块2】API 网关
│ ├── pom.xml
│ ├── src/main/java/
│ │ └── com/canvas/chain/gateway/
│ │ ├── config/
│ │ │ └── NacosConfig.java # Nacos 动态配置
│ │ ├── filter/
│ │ │ ├── AuthFilter.java # 认证过滤器
│ │ │ └── LoggingFilter.java # 日志过滤器
│ │ ├── handler/
│ │ │ └── GlobalExceptionHandler.java
│ │ └── Application.java
│ │
│ ├── resources/
│ │ └── application.yml
│ │
│ └── Dockerfile
│
├── canvas-chain-user-service/ # 【模块3】用户服务
│ ├── pom.xml
│ ├── src/main/java/
│ │ └── com/canvas/chain/user/
│ │ ├── controller/
│ │ │ └── UserController.java # ✅ 用户接口
│ │ ├── service/
│ │ │ ├── UserService.java # 接口
│ │ │ └── impl/
│ │ │ └── UserServiceImpl.java # 实现
│ │ ├── mapper/
│ │ │ └── UserMapper.java # MyBatis mapper
│ │ ├── entity/
│ │ │ └── User.java
│ │ ├── config/
│ │ │ ├── MybatisPlusConfig.java
│ │ │ └── DataSourceConfig.java
│ │ └── Application.java
│ │
│ ├── resources/
│ │ ├── application.yml
│ │ └── application-dev.yml
│ │
│ └── Dockerfile
│
├── canvas-chain-artwork-service/ # 【模块4】创意服务
│ ├── pom.xml
│ ├── src/main/java/
│ │ └── com/canvas/chain/artwork/
│ │ ├── controller/
│ │ ├── service/
│ │ ├── mapper/
│ │ ├── entity/
│ │ └── Application.java
│ │
│ ├── resources/
│ │ └── application.yml
│ │
│ └── Dockerfile
│
├── canvas-chain-auction-service/ # 【模块5】拍卖服务
│ ├── pom.xml
│ ├── src/main/java/
│ │ └── com/canvas/chain/auction/
│ │ ├── controller/
│ │ ├── service/
│ │ ├── mapper/
│ │ ├── entity/
│ │ └── Application.java
│ │
│ ├── resources/
│ │ └── application.yml
│ │
│ └── Dockerfile
│
├── docs/ # 文档
│ ├── database/
│ │ ├── schema.sql # ✅ 数据库设计
│ │ └── init.sql
│ ├── api/
│ │ └── openapi.yaml
│ └── architecture.md
│
├── docker-compose.yml # ✅ 容器编排
├── .gitignore
├── README.md
└── pom.xml # 父 pom
🏗️ 3. 核心 POM 文件设计
3.1 父 pom.xml - 版本统一管理
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.canvas.chain</groupId>
<artifactId>canvas-chain</artifactId>
<version>1.0.0</version>
<packaging>pom</packaging>
<name>CanvasChain - 创意交易所平台</name>
<description>Web3 时代的创意交易平台</description>
<!-- 子模块 -->
<modules>
<module>canvas-chain-common</module>
<module>canvas-chain-gateway</module>
<module>canvas-chain-user-service</module>
<module>canvas-chain-artwork-service</module>
<module>canvas-chain-auction-service</module>
</modules>
<!-- 版本管理 -->
<properties>
<java.version>11</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<!-- Spring Cloud 版本 -->
<spring.cloud.version>2021.0.3</spring.cloud.version>
<spring.boot.version>2.6.3</spring.boot.version>
<!-- 阿里 -->
<alibaba.cloud.version>2021.0.3.0</alibaba.cloud.version>
<alibaba.nacos.version>2.0.4</alibaba.nacos.version>
<!-- 数据库相关 -->
<mybatis.plus.version>3.5.1</mybatis.plus.version>
<mybatis.version>3.5.9</mybatis.version>
<mysql.version>8.0.28</mysql.version>
<!-- Redis -->
<redis.version>2.6.3</redis.version>
<!-- JWT -->
<jjwt.version>0.11.5</jjwt.version>
<!-- 工具类 -->
<hutool.version>5.8.1</hutool.version>
<lombok.version>1.18.22</lombok.version>
<!-- 序列化 -->
<fastjson.version>1.2.79</fastjson.version>
<jackson.version>2.13.1</jackson.version>
<!-- 日志 -->
<log4j.version>2.17.1</log4j.version>
</properties>
<!-- 依赖版本管理 -->
<dependencyManagement>
<dependencies>
<!-- Spring Cloud -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring.boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- 阿里 Cloud -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${alibaba.cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<!-- Nacos -->
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-client</artifactId>
<version>${alibaba.nacos.version}</version>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatis.plus.version}</version>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>${jjwt.version}</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>${jjwt.version}</version>
<scope>runtime</scope>
</dependency>
<!-- Hutool -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>${hutool.version}</version>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<optional>true</optional>
</dependency>
<!-- FastJSON -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>${fastjson.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<!-- 构建插件 -->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring.boot.version}</version>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
3.2 通用模块 pom.xml - canvas-chain-common
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.canvas.chain</groupId>
<artifactId>canvas-chain</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>canvas-chain-common</artifactId>
<name>canvas-chain-common</name>
<description>通用模块 - 公共代码和工具</description>
<dependencies>
<!-- Spring Boot 基础 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Hutool 工具库 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- FastJSON -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
</dependency>
<!-- JWT -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<scope>runtime</scope>
</dependency>
</dependencies>
</project>
3.3 用户服务 pom.xml - canvas-chain-user-service
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.canvas.chain</groupId>
<artifactId>canvas-chain</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>canvas-chain-user-service</artifactId>
<name>canvas-chain-user-service</name>
<description>用户服务 - 用户管理和认证</description>
<dependencies>
<!-- 通用模块 -->
<dependency>
<groupId>com.canvas.chain</groupId>
<artifactId>canvas-chain-common</artifactId>
<version>1.0.0</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Cloud Nacos -->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
<!-- MySQL -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
</project>
📊 4. 数据库设计
4.1 用户表 (user)
CREATE TABLE `user` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID',
`username` VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名',
`email` VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱',
`password_hash` VARCHAR(255) NOT NULL COMMENT '密码哈希',
`avatar_url` VARCHAR(255) COMMENT '头像URL',
`bio` TEXT COMMENT '个人简介',
`role` ENUM('CREATOR', 'COLLECTOR', 'ADMIN') DEFAULT 'COLLECTOR' COMMENT '角色',
`status` ENUM('ACTIVE', 'INACTIVE', 'BANNED') DEFAULT 'ACTIVE' COMMENT '状态',
`follower_count` BIGINT DEFAULT 0 COMMENT '粉丝数',
`following_count` BIGINT DEFAULT 0 COMMENT '关注数',
`historical_rating` DECIMAL(3,2) DEFAULT 5.00 COMMENT '历史评分',
`created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_username (username),
INDEX idx_email (email),
INDEX idx_status (status),
INDEX idx_created_time (created_time)
) COMMENT='用户表';
4.2 创意作品表 (artwork)
CREATE TABLE `artwork` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '作品ID',
`creator_id` BIGINT NOT NULL COMMENT '创作者ID',
`title` VARCHAR(200) NOT NULL COMMENT '作品标题',
`description` TEXT COMMENT '作品描述',
`category` VARCHAR(50) NOT NULL COMMENT '分类',
`cover_image_url` VARCHAR(255) COMMENT '封面图',
`content_url` VARCHAR(255) COMMENT '内容URL(指向存储服务)',
`content_type` ENUM('IMAGE', 'VIDEO', 'TEXT', 'CODE', 'DESIGN') COMMENT '内容类型',
`price` DECIMAL(15,2) DEFAULT 0 COMMENT '定价',
`status` ENUM('DRAFT', 'PUBLISHED', 'SOLD', 'REMOVED') DEFAULT 'DRAFT' COMMENT '状态',
`view_count` BIGINT DEFAULT 0 COMMENT '浏览数',
`like_count` BIGINT DEFAULT 0 COMMENT '点赞数',
`rating_score` DECIMAL(3,2) DEFAULT 0 COMMENT '评分',
`total_sales` DECIMAL(15,2) DEFAULT 0 COMMENT '总销售额',
`settled_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '已结算收入',
`unsettled_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '未结算收入',
`created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_creator_id (creator_id),
INDEX idx_category (category),
INDEX idx_status (status),
INDEX idx_created_time (created_time),
INDEX idx_view_count (view_count),
FOREIGN KEY (creator_id) REFERENCES `user`(id)
) COMMENT='创意作品表';
4.3 拍卖表 (auction)
CREATE TABLE `auction` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '拍卖ID',
`artwork_id` BIGINT NOT NULL COMMENT '作品ID',
`start_price` DECIMAL(15,2) NOT NULL COMMENT '起价',
`current_price` DECIMAL(15,2) DEFAULT 0 COMMENT '当前价格',
`reserve_price` DECIMAL(15,2) COMMENT '底价',
`highest_bidder_id` BIGINT COMMENT '最高出价者ID',
`auction_type` ENUM('ENGLISH', 'DUTCH', 'SEALED_BID') DEFAULT 'ENGLISH' COMMENT '拍卖方式',
`status` ENUM('PENDING', 'STARTED', 'ENDED', 'SETTLED') DEFAULT 'PENDING' COMMENT '拍卖状态',
`start_time` TIMESTAMP COMMENT '开始时间',
`end_time` TIMESTAMP COMMENT '结束时间',
`bid_count` INT DEFAULT 0 COMMENT '出价次数',
`created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_artwork_id (artwork_id),
INDEX idx_status (status),
INDEX idx_end_time (end_time),
INDEX idx_created_time (created_time),
FOREIGN KEY (artwork_id) REFERENCES `artwork`(id),
FOREIGN KEY (highest_bidder_id) REFERENCES `user`(id)
) COMMENT='拍卖表';
4.4 出价记录表 (bid)
CREATE TABLE `bid` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '出价ID',
`auction_id` BIGINT NOT NULL COMMENT '拍卖ID',
`bidder_id` BIGINT NOT NULL COMMENT '出价者ID',
`bid_amount` DECIMAL(15,2) NOT NULL COMMENT '出价金额',
`bid_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '出价时间',
`is_highest` TINYINT(1) DEFAULT 0 COMMENT '是否最高价',
INDEX idx_auction_id (auction_id),
INDEX idx_bidder_id (bidder_id),
INDEX idx_bid_time (bid_time),
FOREIGN KEY (auction_id) REFERENCES `auction`(id),
FOREIGN KEY (bidder_id) REFERENCES `user`(id)
) COMMENT='出价记录表';
4.5 交易表 (transaction)
CREATE TABLE `transaction` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '交易ID',
`buyer_id` BIGINT NOT NULL COMMENT '买家ID',
`seller_id` BIGINT NOT NULL COMMENT '卖家ID',
`artwork_id` BIGINT NOT NULL COMMENT '作品ID',
`transaction_type` ENUM('DIRECT_PURCHASE', 'AUCTION_WIN', 'BLINDBOX', 'COMBINED') COMMENT '交易类型',
`transaction_amount` DECIMAL(15,2) NOT NULL COMMENT '交易金额',
`platform_commission` DECIMAL(15,2) DEFAULT 0 COMMENT '平台抽成',
`seller_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '卖家收入',
`status` ENUM('PENDING', 'COMPLETED', 'FAILED', 'REFUNDED') DEFAULT 'PENDING' COMMENT '交易状态',
`created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`completed_time` TIMESTAMP COMMENT '完成时间',
`updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_buyer_id (buyer_id),
INDEX idx_seller_id (seller_id),
INDEX idx_artwork_id (artwork_id),
INDEX idx_status (status),
INDEX idx_created_time (created_time),
FOREIGN KEY (buyer_id) REFERENCES `user`(id),
FOREIGN KEY (seller_id) REFERENCES `user`(id),
FOREIGN KEY (artwork_id) REFERENCES `artwork`(id)
) COMMENT='交易表';
4.6 钱包表 (wallet)
CREATE TABLE `wallet` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '钱包ID',
`user_id` BIGINT NOT NULL UNIQUE COMMENT '用户ID',
`balance` DECIMAL(15,2) DEFAULT 0 COMMENT '余额',
`locked_balance` DECIMAL(15,2) DEFAULT 0 COMMENT '冻结余额',
`total_recharge` DECIMAL(15,2) DEFAULT 0 COMMENT '总充值',
`total_withdraw` DECIMAL(15,2) DEFAULT 0 COMMENT '总提现',
`created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_user_id (user_id),
FOREIGN KEY (user_id) REFERENCES `user`(id)
) COMMENT='用户钱包表';
4.7 投票表 (vote)
CREATE TABLE `vote` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '投票ID',
`voter_id` BIGINT NOT NULL COMMENT '投票者ID',
`artwork_id` BIGINT NOT NULL COMMENT '作品ID',
`vote_weight` DECIMAL(5,2) DEFAULT 1.00 COMMENT '投票权重',
`created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '投票时间',
UNIQUE KEY uk_voter_artwork (voter_id, artwork_id),
INDEX idx_artwork_id (artwork_id),
INDEX idx_voter_id (voter_id),
INDEX idx_created_time (created_time),
FOREIGN KEY (voter_id) REFERENCES `user`(id),
FOREIGN KEY (artwork_id) REFERENCES `artwork`(id)
) COMMENT='投票表';
4.8 盲盒表 (blind_box)
CREATE TABLE `blind_box` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '盲盒ID',
`creator_id` BIGINT NOT NULL COMMENT '创作者ID',
`title` VARCHAR(200) NOT NULL COMMENT '盲盒标题',
`description` TEXT COMMENT '盲盒描述',
`cover_image_url` VARCHAR(255) COMMENT '封面图',
`box_price` DECIMAL(15,2) NOT NULL COMMENT '盲盒价格',
`total_boxes` INT NOT NULL COMMENT '总盲盒数',
`remaining_boxes` INT NOT NULL COMMENT '剩余盲盒数',
`status` ENUM('CREATED', 'ACTIVE', 'SOLD_OUT', 'CLOSED') DEFAULT 'CREATED' COMMENT '状态',
`created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_creator_id (creator_id),
INDEX idx_status (status),
INDEX idx_created_time (created_time),
FOREIGN KEY (creator_id) REFERENCES `user`(id)
) COMMENT='盲盒表';
4.9 盲盒奖品表 (blind_box_prize)
CREATE TABLE `blind_box_prize` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '奖品ID',
`blind_box_id` BIGINT NOT NULL COMMENT '盲盒ID',
`artwork_id` BIGINT NOT NULL COMMENT '作品ID',
`rarity` ENUM('COMMON', 'RARE', 'EPIC', 'LEGENDARY') COMMENT '稀有度',
`probability_weight` INT DEFAULT 100 COMMENT '概率权重',
`quantity` INT DEFAULT 1 COMMENT '数量',
`remaining_quantity` INT COMMENT '剩余数量',
INDEX idx_blind_box_id (blind_box_id),
INDEX idx_artwork_id (artwork_id),
FOREIGN KEY (blind_box_id) REFERENCES `blind_box`(id),
FOREIGN KEY (artwork_id) REFERENCES `artwork`(id)
) COMMENT='盲盒奖品表';
4.10 盲盒开箱记录表 (blind_box_opening)
CREATE TABLE `blind_box_opening` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '开箱记录ID',
`blind_box_id` BIGINT NOT NULL COMMENT '盲盒ID',
`user_id` BIGINT NOT NULL COMMENT '用户ID',
`prize_id` BIGINT NOT NULL COMMENT '获得的奖品ID',
`artwork_id` BIGINT NOT NULL COMMENT '获得的作品ID',
`opened_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '开箱时间',
INDEX idx_blind_box_id (blind_box_id),
INDEX idx_user_id (user_id),
INDEX idx_opened_time (opened_time),
FOREIGN KEY (blind_box_id) REFERENCES `blind_box`(id),
FOREIGN KEY (user_id) REFERENCES `user`(id),
FOREIGN KEY (prize_id) REFERENCES `blind_box_prize`(id),
FOREIGN KEY (artwork_id) REFERENCES `artwork`(id)
) COMMENT='盲盒开箱记录表';
4.11 NFT 虚拟资产表 (nft)
CREATE TABLE `nft` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT 'NFT ID',
`nft_token_id` VARCHAR(100) NOT NULL UNIQUE COMMENT 'NFT Token ID',
`artwork_id` BIGINT NOT NULL COMMENT '对应作品ID',
`owner_id` BIGINT NOT NULL COMMENT '当前所有者ID',
`creator_id` BIGINT NOT NULL COMMENT '创作者ID',
`metadata_uri` VARCHAR(255) COMMENT '元数据URI',
`minting_price` DECIMAL(15,2) COMMENT '铸造价格',
`mint_time` TIMESTAMP COMMENT '铸造时间',
`status` ENUM('MINTED', 'LISTED', 'TRANSFERRED', 'BURNED') DEFAULT 'MINTED' COMMENT '状态',
`created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_artwork_id (artwork_id),
INDEX idx_owner_id (owner_id),
INDEX idx_creator_id (creator_id),
INDEX idx_status (status),
FOREIGN KEY (artwork_id) REFERENCES `artwork`(id),
FOREIGN KEY (owner_id) REFERENCES `user`(id),
FOREIGN KEY (creator_id) REFERENCES `user`(id)
) COMMENT='NFT虚拟资产表';
4.12 聚合拍卖表 (combined_auction)
CREATE TABLE `combined_auction` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '聚合拍卖ID',
`title` VARCHAR(200) NOT NULL COMMENT '拍卖标题',
`description` TEXT COMMENT '描述',
`start_price` DECIMAL(15,2) NOT NULL COMMENT '起价',
`current_price` DECIMAL(15,2) DEFAULT 0 COMMENT '当前价格',
`highest_bidder_id` BIGINT COMMENT '最高出价者ID',
`status` ENUM('PENDING', 'STARTED', 'ENDED', 'SETTLED') DEFAULT 'PENDING' COMMENT '状态',
`start_time` TIMESTAMP COMMENT '开始时间',
`end_time` TIMESTAMP COMMENT '结束时间',
`artwork_count` INT COMMENT '作品数量',
`bid_count` INT DEFAULT 0 COMMENT '出价次数',
`created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
INDEX idx_status (status),
INDEX idx_end_time (end_time),
INDEX idx_created_time (created_time),
FOREIGN KEY (highest_bidder_id) REFERENCES `user`(id)
) COMMENT='聚合拍卖表';
4.13 聚合拍卖作品表 (combined_auction_item)
CREATE TABLE `combined_auction_item` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '项目ID',
`combined_auction_id` BIGINT NOT NULL COMMENT '聚合拍卖ID',
`artwork_id` BIGINT NOT NULL COMMENT '作品ID',
`item_weight` INT DEFAULT 100 COMMENT '项目权重',
INDEX idx_combined_auction_id (combined_auction_id),
INDEX idx_artwork_id (artwork_id),
FOREIGN KEY (combined_auction_id) REFERENCES `combined_auction`(id),
FOREIGN KEY (artwork_id) REFERENCES `artwork`(id)
) COMMENT='聚合拍卖作品表';
4.14 平台账户表 (platform_account)
CREATE TABLE `platform_account` (
`id` BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT '账户ID',
`account_name` VARCHAR(100) NOT NULL UNIQUE COMMENT '账户名',
`total_commission` DECIMAL(15,2) DEFAULT 0 COMMENT '总佣金',
`total_platform_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '总收入',
`settled_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '已结算收入',
`unsettled_revenue` DECIMAL(15,2) DEFAULT 0 COMMENT '未结算收入',
`created_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
) COMMENT='平台账户表';
4.15 初始化脚本 (init.sql)
-- 创建数据库
CREATE DATABASE IF NOT EXISTS canvas_chain CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE canvas_chain;
-- 创建所有表...
-- (上述所有表定义)
-- 初始化数据
INSERT INTO `platform_account` (account_name, total_commission, created_time)
VALUES ('platform_main', 0, NOW());
-- 插入平台管理员用户
INSERT INTO `user` (username, email, password_hash, role, status)
VALUES ('admin', 'admin@canvas-chain.com', '$2a$10$...hashed...', 'ADMIN', 'ACTIVE');
🔐 5. 核心实体类设计
5.1 User 实体 - canvas-chain-common
package com.canvas.chain.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("user")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 用户ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户名
*/
private String username;
/**
* 邮箱
*/
private String email;
/**
* 密码哈希
*/
private String passwordHash;
/**
* 头像URL
*/
private String avatarUrl;
/**
* 个人简介
*/
private String bio;
/**
* 用户角色:CREATOR(创作者)、COLLECTOR(收藏家)、ADMIN(管理员)
*/
private String role;
/**
* 账户状态:ACTIVE、INACTIVE、BANNED
*/
private String status;
/**
* 粉丝数
*/
private Long followerCount;
/**
* 关注数
*/
private Long followingCount;
/**
* 历史评分(1-5分)
*/
private BigDecimal historicalRating;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
}
5.2 Artwork 实体
package com.canvas.chain.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("artwork")
public class Artwork implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 作品ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 创作者ID
*/
private Long creatorId;
/**
* 作品标题
*/
private String title;
/**
* 作品描述
*/
private String description;
/**
* 分类
*/
private String category;
/**
* 封面图URL
*/
private String coverImageUrl;
/**
* 内容URL
*/
private String contentUrl;
/**
* 内容类型:IMAGE、VIDEO、TEXT、CODE、DESIGN
*/
private String contentType;
/**
* 定价
*/
private BigDecimal price;
/**
* 作品状态:DRAFT、PUBLISHED、SOLD、REMOVED
*/
private String status;
/**
* 浏览数
*/
private Long viewCount;
/**
* 点赞数
*/
private Long likeCount;
/**
* 评分
*/
private BigDecimal ratingScore;
/**
* 总销售额
*/
private BigDecimal totalSales;
/**
* 已结算收入
*/
private BigDecimal settledRevenue;
/**
* 未结算收入
*/
private BigDecimal unsettledRevenue;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
}
5.3 Auction 实体
package com.canvas.chain.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("auction")
public class Auction implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 拍卖ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 作品ID
*/
private Long artworkId;
/**
* 起价
*/
private BigDecimal startPrice;
/**
* 当前价格
*/
private BigDecimal currentPrice;
/**
* 底价
*/
private BigDecimal reservePrice;
/**
* 最高出价者ID
*/
private Long highestBidderId;
/**
* 拍卖方式:ENGLISH、DUTCH、SEALED_BID
*/
private String auctionType;
/**
* 拍卖状态:PENDING、STARTED、ENDED、SETTLED
*/
private String status;
/**
* 开始时间
*/
private LocalDateTime startTime;
/**
* 结束时间
*/
private LocalDateTime endTime;
/**
* 出价次数
*/
private Integer bidCount;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
}
5.4 Transaction 实体
package com.canvas.chain.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("transaction")
public class Transaction implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 交易ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 买家ID
*/
private Long buyerId;
/**
* 卖家ID
*/
private Long sellerId;
/**
* 作品ID
*/
private Long artworkId;
/**
* 交易类型:DIRECT_PURCHASE、AUCTION_WIN、BLINDBOX、COMBINED
*/
private String transactionType;
/**
* 交易金额
*/
private BigDecimal transactionAmount;
/**
* 平台抽成
*/
private BigDecimal platformCommission;
/**
* 卖家收入
*/
private BigDecimal sellerRevenue;
/**
* 交易状态:PENDING、COMPLETED、FAILED、REFUNDED
*/
private String status;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
/**
* 完成时间
*/
private LocalDateTime completedTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
}
5.5 Wallet 实体
package com.canvas.chain.entity;
import com.baomidou.mybatisplus.annotation.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@TableName("wallet")
public class Wallet implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 钱包ID
*/
@TableId(type = IdType.AUTO)
private Long id;
/**
* 用户ID
*/
private Long userId;
/**
* 余额
*/
private BigDecimal balance;
/**
* 冻结余额
*/
private BigDecimal lockedBalance;
/**
* 总充值
*/
private BigDecimal totalRecharge;
/**
* 总提现
*/
private BigDecimal totalWithdraw;
/**
* 创建时间
*/
@TableField(fill = FieldFill.INSERT)
private LocalDateTime createdTime;
/**
* 更新时间
*/
@TableField(fill = FieldFill.INSERT_UPDATE)
private LocalDateTime updatedTime;
}
📋 6. 枚举类定义
6.1 用户角色枚举 - UserRoleEnum.java
package com.canvas.chain.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum UserRoleEnum {
/**
* 创作者 - 可以发布创意作品
*/
CREATOR("CREATOR", "创作者"),
/**
* 收藏家 - 可以购买和收藏作品
*/
COLLECTOR("COLLECTOR", "收藏家"),
/**
* 管理员 - 系统管理员
*/
ADMIN("ADMIN", "管理员");
private final String code;
private final String desc;
public static UserRoleEnum getValue(String code) {
for (UserRoleEnum userRole : UserRoleEnum.values()) {
if (userRole.code.equals(code)) {
return userRole;
}
}
return null;
}
}
6.2 用户状态枚举 - UserStatusEnum.java
package com.canvas.chain.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum UserStatusEnum {
/**
* 激活 - 正常使用
*/
ACTIVE("ACTIVE", "激活"),
/**
* 未激活 - 等待邮箱确认
*/
INACTIVE("INACTIVE", "未激活"),
/**
* 被禁用 - 账户被冻结
*/
BANNED("BANNED", "被禁用");
private final String code;
private final String desc;
public static UserStatusEnum getValue(String code) {
for (UserStatusEnum status : UserStatusEnum.values()) {
if (status.code.equals(code)) {
return status;
}
}
return null;
}
}
6.3 拍卖状态枚举 - AuctionStatusEnum.java
package com.canvas.chain.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum AuctionStatusEnum {
/**
* 待开始
*/
PENDING("PENDING", "待开始"),
/**
* 进行中
*/
STARTED("STARTED", "进行中"),
/**
* 已结束
*/
ENDED("ENDED", "已结束"),
/**
* 已结算
*/
SETTLED("SETTLED", "已结算");
private final String code;
private final String desc;
public static AuctionStatusEnum getValue(String code) {
for (AuctionStatusEnum status : AuctionStatusEnum.values()) {
if (status.code.equals(code)) {
return status;
}
}
return null;
}
}
6.4 交易类型枚举 - TransactionTypeEnum.java
package com.canvas.chain.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum TransactionTypeEnum {
/**
* 直接购买
*/
DIRECT_PURCHASE("DIRECT_PURCHASE", "直接购买"),
/**
* 拍卖获胜
*/
AUCTION_WIN("AUCTION_WIN", "拍卖获胜"),
/**
* 盲盒获得
*/
BLINDBOX("BLINDBOX", "盲盒获得"),
/**
* 组合购买
*/
COMBINED("COMBINED", "组合购买");
private final String code;
private final String desc;
public static TransactionTypeEnum getValue(String code) {
for (TransactionTypeEnum type : TransactionTypeEnum.values()) {
if (type.code.equals(code)) {
return type;
}
}
return null;
}
}
6.5 交易状态枚举 - TransactionStatusEnum.java
package com.canvas.chain.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum TransactionStatusEnum {
/**
* 待支付
*/
PENDING("PENDING", "待支付"),
/**
* 已完成
*/
COMPLETED("COMPLETED", "已完成"),
/**
* 已失败
*/
FAILED("FAILED", "已失败"),
/**
* 已退款
*/
REFUNDED("REFUNDED", "已退款");
private final String code;
private final String desc;
public static TransactionStatusEnum getValue(String code) {
for (TransactionStatusEnum status : TransactionStatusEnum.values()) {
if (status.code.equals(code)) {
return status;
}
}
return null;
}
}
📍 7. DTO 数据传输对象设计
7.1 用户登录请求 DTO - LoginRequest.java
package com.canvas.chain.dto.request;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Pattern;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoginRequest {
/**
* 用户名或邮箱
*/
@NotBlank(message = "用户名或邮箱不能为空")
private String username;
/**
* 密码
*/
@NotBlank(message = "密码不能为空")
private String password;
}
7.2 用户注册请求 DTO - RegisterRequest.java
package com.canvas.chain.dto.request;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.*;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RegisterRequest {
/**
* 用户名(3-20字符)
*/
@NotBlank(message = "用户名不能为空")
@Size(min = 3, max = 20, message = "用户名长度需要3-20个字符")
private String username;
/**
* 邮箱
*/
@NotBlank(message = "邮箱不能为空")
@Email(message = "邮箱格式不正确")
private String email;
/**
* 密码(至少8位,包含大小写字母和数字)
*/
@NotBlank(message = "密码不能为空")
@Size(min = 8, message = "密码至少需要8个字符")
@Pattern(regexp = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)[a-zA-Z\\d@$!%*?&]{8,}$",
message = "密码必须包含大小写字母和数字")
private String password;
/**
* 确认密码
*/
@NotBlank(message = "确认密码不能为空")
private String confirmPassword;
/**
* 用户角色
*/
@NotBlank(message = "用户角色不能为空")
private String role; // CREATOR 或 COLLECTOR
}
7.3 用户登录响应 DTO - LoginResponse.java
package com.canvas.chain.dto.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoginResponse {
/**
* JWT 令牌
*/
private String token;
/**
* 刷新令牌
*/
private String refreshToken;
/**
* 用户ID
*/
private Long userId;
/**
* 用户名
*/
private String username;
/**
* 用户邮箱
*/
private String email;
/**
* 用户角色
*/
private String role;
/**
* 令牌有效期(秒)
*/
private Long expiresIn;
}
7.4 用户信息响应 DTO - UserResponse.java
package com.canvas.chain.dto.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserResponse {
/**
* 用户ID
*/
private Long id;
/**
* 用户名
*/
private String username;
/**
* 邮箱
*/
private String email;
/**
* 头像URL
*/
private String avatarUrl;
/**
* 个人简介
*/
private String bio;
/**
* 用户角色
*/
private String role;
/**
* 账户状态
*/
private String status;
/**
* 粉丝数
*/
private Long followerCount;
/**
* 关注数
*/
private Long followingCount;
/**
* 历史评分
*/
private BigDecimal historicalRating;
/**
* 创建时间
*/
private LocalDateTime createdTime;
}
📋 7. DTO 数据传输对象设计(续)
7.5 通用响应包装 DTO - ApiResponse.java
package com.canvas.chain.dto.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ApiResponse<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 响应码
*/
private String code;
/**
* 响应消息
*/
private String message;
/**
* 响应数据
*/
private T data;
/**
* 时间戳
*/
private Long timestamp;
/**
* 成功响应
*/
public static <T> ApiResponse<T> success(T data) {
return ApiResponse.<T>builder()
.code("200")
.message("成功")
.data(data)
.timestamp(System.currentTimeMillis())
.build();
}
/**
* 成功响应(带消息)
*/
public static <T> ApiResponse<T> success(String message, T data) {
return ApiResponse.<T>builder()
.code("200")
.message(message)
.data(data)
.timestamp(System.currentTimeMillis())
.build();
}
/**
* 失败响应
*/
public static <T> ApiResponse<T> fail(String code, String message) {
return ApiResponse.<T>builder()
.code(code)
.message(message)
.data(null)
.timestamp(System.currentTimeMillis())
.build();
}
/**
* 失败响应(带数据)
*/
public static <T> ApiResponse<T> fail(String code, String message, T data) {
return ApiResponse.<T>builder()
.code(code)
.message(message)
.data(data)
.timestamp(System.currentTimeMillis())
.build();
}
}
7.6 分页响应 DTO - PageResponse.java
package com.canvas.chain.dto.response;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PageResponse<T> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 当前页码
*/
private Long current;
/**
* 每页数量
*/
private Long size;
/**
* 总记录数
*/
private Long total;
/**
* 总页数
*/
private Long pages;
/**
* 数据列表
*/
private List<T> records;
/**
* 是否有下一页
*/
private Boolean hasNext;
/**
* 是否有上一页
*/
private Boolean hasPrevious;
}
🛠️ 8. 工具类设计
8.1 JWT 工具类 - JwtUtil.java
package com.canvas.chain.util;
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@Component
public class JwtUtil {
/**
* JWT 秘钥(最少256位)
*/
@Value("${jwt.secret:canvas-chain-secret-key-minimum-256-bits-long}")
private String secret;
/**
* JWT 过期时间(单位:秒,默认24小时)
*/
@Value("${jwt.expiration:86400}")
private Long expiration;
/**
* 刷新令牌过期时间(单位:秒,默认7天)
*/
@Value("${jwt.refresh-expiration:604800}")
private Long refreshExpiration;
/**
* 生成 JWT Token
*/
public String generateToken(Long userId, String username, String role) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("username", username);
claims.put("role", role);
return createToken(claims, username, expiration);
}
/**
* 生成刷新令牌
*/
public String generateRefreshToken(Long userId, String username) {
Map<String, Object> claims = new HashMap<>();
claims.put("userId", userId);
claims.put("type", "refresh");
return createToken(claims, username, refreshExpiration);
}
/**
* 创建 Token
*/
private String createToken(Map<String, Object> claims,
String subject,
Long expiration) {
Date now = new Date();
Date expiryDate = new Date(now.getTime() + expiration * 1000);
return Jwts.builder()
.setClaims(claims)
.setSubject(subject)
.setIssuedAt(now)
.setExpiration(expiryDate)
.signWith(Keys.hmacShaKeyFor(
secret.getBytes(StandardCharsets.UTF_8)),
SignatureAlgorithm.HS256)
.compact();
}
/**
* 从 Token 中获取用户ID
*/
public Long getUserIdFromToken(String token) {
Claims claims = getAllClaimsFromToken(token);
return claims.get("userId", Long.class);
}
/**
* 从 Token 中获取用户名
*/
public String getUsernameFromToken(String token) {
Claims claims = getAllClaimsFromToken(token);
return claims.getSubject();
}
/**
* 从 Token 中获取角色
*/
public String getRoleFromToken(String token) {
Claims claims = getAllClaimsFromToken(token);
return claims.get("role", String.class);
}
/**
* 获取所有声明
*/
private Claims getAllClaimsFromToken(String token) {
try {
return Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(
secret.getBytes(StandardCharsets.UTF_8)))
.build()
.parseClaimsJws(token)
.getBody();
} catch (SecurityException e) {
log.error("Invalid JWT signature: {}", e.getMessage());
throw new JwtException("Invalid JWT signature", e);
} catch (MalformedJwtException e) {
log.error("Invalid JWT token: {}", e.getMessage());
throw new JwtException("Invalid JWT token", e);
} catch (ExpiredJwtException e) {
log.error("Expired JWT token: {}", e.getMessage());
throw new JwtException("Expired JWT token", e);
} catch (UnsupportedJwtException e) {
log.error("Unsupported JWT token: {}", e.getMessage());
throw new JwtException("Unsupported JWT token", e);
} catch (IllegalArgumentException e) {
log.error("JWT claims string is empty: {}", e.getMessage());
throw new JwtException("JWT claims string is empty", e);
}
}
/**
* 验证 Token 是否有效
*/
public Boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(Keys.hmacShaKeyFor(
secret.getBytes(StandardCharsets.UTF_8)))
.build()
.parseClaimsJws(token);
return true;
} catch (JwtException | IllegalArgumentException e) {
log.error("JWT validation failed: {}", e.getMessage());
return false;
}
}
/**
* 获取 Token 过期时间
*/
public Date getExpirationDateFromToken(String token) {
Claims claims = getAllClaimsFromToken(token);
return claims.getExpiration();
}
/**
* 判断 Token 是否过期
*/
private Boolean isTokenExpired(String token) {
try {
Date expiration = getExpirationDateFromToken(token);
return expiration.before(new Date());
} catch (ExpiredJwtException e) {
return true;
}
}
}
8.2 密码加密工具类 - EncryptUtil.java
package com.canvas.chain.util;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Component;
@Component
public class EncryptUtil {
private static final BCryptPasswordEncoder passwordEncoder =
new BCryptPasswordEncoder();
/**
* 密码加密
*/
public static String encodePassword(String password) {
return passwordEncoder.encode(password);
}
/**
* 密码验证
*/
public static Boolean matchPassword(String rawPassword,
String encodedPassword) {
return passwordEncoder.matches(rawPassword, encodedPassword);
}
}
8.3 ID 生成工具类 - IdGenerator.java
package com.canvas.chain.util;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.util.IdUtil;
import org.springframework.stereotype.Component;
@Component
public class IdGenerator {
private static final Snowflake snowflake = IdUtil.getSnowflake(1, 1);
/**
* 生成分布式ID(雪花算法)
*/
public static Long nextId() {
return snowflake.nextId();
}
/**
* 生成 UUID
*/
public static String nextUUID() {
return IdUtil.simpleUUID();
}
/**
* 生成 NFT Token ID
*/
public static String generateNFTTokenId() {
return "nft_" + System.currentTimeMillis() + "_" + IdUtil.simpleUUID();
}
}
8.4 日期工具类 - DateUtil.java
package com.canvas.chain.util;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;
public class DateUtil {
private static final DateTimeFormatter DEFAULT_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
/**
* 获取当前时间戳(毫秒)
*/
public static Long getCurrentTimeMillis() {
return System.currentTimeMillis();
}
/**
* 获取当前时间戳(秒)
*/
public static Long getCurrentTimeSeconds() {
return System.currentTimeMillis() / 1000;
}
/**
* LocalDateTime 转换为字符串
*/
public static String format(LocalDateTime dateTime) {
return dateTime.format(DEFAULT_FORMATTER);
}
/**
* LocalDateTime 转换为 Date
*/
public static Date toDate(LocalDateTime dateTime) {
return Date.from(dateTime.atZone(ZoneId.systemDefault()).toInstant());
}
/**
* Date 转换为 LocalDateTime
*/
public static LocalDateTime toLocalDateTime(Date date) {
return date.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
}
}
❌ 9. 异常处理设计
9.1 业务异常基类 - BusinessException.java
package com.canvas.chain.exception;
import lombok.Getter;
@Getter
public class BusinessException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 错误码
*/
private String code;
/**
* 错误消息
*/
private String message;
public BusinessException(String message) {
super(message);
this.code = "500";
this.message = message;
}
public BusinessException(String code, String message) {
super(message);
this.code = code;
this.message = message;
}
public BusinessException(String code, String message, Throwable cause) {
super(message, cause);
this.code = code;
this.message = message;
}
}
9.2 认证异常 - AuthException.java
package com.canvas.chain.exception;
public class AuthException extends BusinessException {
private static final long serialVersionUID = 1L;
public AuthException(String message) {
super("401", message);
}
public AuthException(String code, String message) {
super(code, message);
}
}
9.3 参数验证异常 - ValidationException.java
package com.canvas.chain.exception;
public class ValidationException extends BusinessException {
private static final long serialVersionUID = 1L;
public ValidationException(String message) {
super("400", message);
}
}
9.4 资源不存在异常 - ResourceNotFoundException.java
package com.canvas.chain.exception;
public class ResourceNotFoundException extends BusinessException {
private static final long serialVersionUID = 1L;
public ResourceNotFoundException(String message) {
super("404", message);
}
}
9.5 全局异常处理 - GlobalExceptionHandler.java
package com.canvas.chain.config;
import com.canvas.chain.dto.response.ApiResponse;
import com.canvas.chain.exception.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.stream.Collectors;
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {
/**
* 处理业务异常
*/
@ExceptionHandler(BusinessException.class)
@ResponseStatus(HttpStatus.OK)
public ApiResponse<?> handleBusinessException(BusinessException e) {
log.error("业务异常: {}", e.getMessage(), e);
return ApiResponse.fail(e.getCode(), e.getMessage());
}
/**
* 处理认证异常
*/
@ExceptionHandler(AuthException.class)
@ResponseStatus(HttpStatus.UNAUTHORIZED)
public ApiResponse<?> handleAuthException(AuthException e) {
log.error("认证异常: {}", e.getMessage(), e);
return ApiResponse.fail(e.getCode(), e.getMessage());
}
/**
* 处理资源不存在异常
*/
@ExceptionHandler(ResourceNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public ApiResponse<?> handleResourceNotFoundException(
ResourceNotFoundException e) {
log.error("资源不存在异常: {}", e.getMessage(), e);
return ApiResponse.fail(e.getCode(), e.getMessage());
}
/**
* 处理参数验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiResponse<?> handleValidationException(
MethodArgumentNotValidException e) {
BindingResult bindingResult = e.getBindingResult();
String errorMessage = bindingResult.getFieldErrors()
.stream()
.map(error -> error.getField() + ": " +
error.getDefaultMessage())
.collect(Collectors.joining("; "));
log.error("参数验证异常: {}", errorMessage);
return ApiResponse.fail("400", errorMessage);
}
/**
* 处理未知异常
*/
@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiResponse<?> handleException(Exception e) {
log.error("系统异常: {}", e.getMessage(), e);
return ApiResponse.fail("500", "系统内部错误");
}
}
⚙️ 10. 配置类设计
10.1 MyBatis Plus 配置 - MybatisPlusConfig.java
package com.canvas.chain.user.config;
import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisPlusConfig {
/**
* 分页插件
*/
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(
new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
10.2 Web MVC 配置 - WebMvcConfig.java
package com.canvas.chain.user.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.List;
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
/**
* 配置消息转换器
*/
@Override
public void configureMessageConverters(
List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter converter =
new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(List.of(
MediaType.APPLICATION_JSON,
MediaType.APPLICATION_JSON_UTF8
));
converters.add(0, converter);
}
}
10.3 REST 模板配置 - RestTemplateConfig.java
package com.canvas.chain.common.config;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
import java.time.Duration;
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder
.setConnectTimeout(Duration.ofSeconds(5))
.setReadTimeout(Duration.ofSeconds(10))
.build();
}
}
📝 11. application.yml 配置文件
11.1 通用配置 - canvas-chain-common/resources/application-common.yml
# 公共配置
spring:
application:
name: canvas-chain
jackson:
default-property-inclusion: non_null
serialization:
write-dates-as-timestamps: false
indent-output: true
mvc:
throw-exception-if-no-handler-found: true
web:
resources:
add-mappings: false
# JWT 配置
jwt:
secret: canvas-chain-secret-key-minimum-256-bits-long-for-security
expiration: 86400 # 24 小时
refresh-expiration: 604800 # 7 天
# 日志配置
logging:
level:
root: INFO
com.canvas.chain: DEBUG
pattern:
console: "%d{yyyy-MM-dd HH:mm:ss} - %msg%n"
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %msg%n"
file:
name: logs/canvas-chain.log
max-size: 10MB
max-history: 30
11.2 用户服务配置 - canvas-chain-user-service/resources/application.yml
spring:
application:
name: canvas-user-service
profiles:
active: dev
cloud:
nacos:
discovery:
server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
namespace: public
group: DEFAULT_GROUP
config:
server-addr: ${NACOS_SERVER_ADDR:localhost:8848}
namespace: public
group: DEFAULT_GROUP
file-extension: yml
prefix: ${spring.application.name}
datasource:
url: jdbc:mysql://${MYSQL_HOST:localhost}:${MYSQL_PORT:3306}/canvas_chain?useUnicode=true&characterEncoding=utf8mb4&useSSL=false&serverTimezone=Asia/Shanghai
username: ${MYSQL_USER:root}
password: ${MYSQL_PASSWORD:root123456}
driver-class-name: com.mysql.cj.jdbc.Driver
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 30000
idle-timeout: 600000
max-lifetime: 1800000
redis:
host: ${REDIS_HOST:localhost}
port: ${REDIS_PORT:6379}
password: ${REDIS_PASSWORD:}
timeout: 60000ms
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
jpa:
show-sql: false
hibernate:
ddl-auto: validate
# MyBatis Plus 配置
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
mapper-locations: classpath*:mapper/**/*.xml
type-aliases-package: com.canvas.chain.entity
global-config:
banner: false
db-config:
id-type: auto
table-underline: true
logic-delete-value: 1
logic-not-delete-value: 0
# 服务器配置
server:
port: 8001
servlet:
context-path: /
tomcat:
threads:
max: 200
min-spare: 10
# 平台信息
platform:
commission-rate: 0.10 # 平台抽成 10%
min-withdraw: 100 # 最低提现金额
max-withdraw: 100000 # 最高提现金额
11.3 网关配置 - canvas-chain-gateway/resources/application.yml
spring:
application:
name: canvas-gateway
cloud:
gateway:
routes:
# 用户服务路由
- id: user-service
uri: lb://canvas-user-service
predicates:
- Path=/api/user/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
key-resolver: "#{@userKeyResolver}"
redis-rate-limiter.replenish-rate: 100
redis-rate-limiter.requested-tokens: 1
# 创意服务路由
- id: artwork-service
uri: lb://canvas-artwork-service
predicates:
- Path=/api/artwork/**
filters:
- StripPrefix=1
- name: RequestRateLimiter
args:
key-resolver: "#{@userKeyResolver}"
redis-rate-limiter.replenish-rate: 50
redis-rate-limiter.requested-tokens: 1
globalcors:
cors-configurations:
'[/**]':
allow-credentials: true
allowed-headers: "*"
allowed-methods:
- GET
- POST
- PUT
- DELETE
- OPTIONS
allowed-origins: "*"
max-age: 3600
nacos:
discovery:
server-addr: localhost:8848
namespace: public
group: DEFAULT_GROUP
server:
port: 9000
logging:
level:
org.springframework.cloud.gateway: DEBUG
📌 12. 常量定义
12.1 系统常量 - SystemConstant.java
package com.canvas.chain.constant;
public class SystemConstant {
/**
* 平台名称
*/
public static final String PLATFORM_NAME = "CanvasChain";
/**
* 平台版本
*/
public static final String PLATFORM_VERSION = "1.0.0";
/**
* JWT Token 前缀
*/
public static final String TOKEN_HEADER = "Authorization";
/**
* JWT Token 类型
*/
public static final String TOKEN_TYPE = "Bearer";
/**
* 默认头像
*/
public static final String DEFAULT_AVATAR =
"https://api.canvas-chain.com/assets/default-avatar.png";
/**
* 平台佣金比率
*/
public static final Double PLATFORM_COMMISSION_RATE = 0.10; // 10%
/**
* 最低充值金额
*/
public static final Double MIN_RECHARGE_AMOUNT = 1.00;
/**
* 最高充值金额
*/
public static final Double MAX_RECHARGE_AMOUNT = 100000.00;
/**
* 最低提现金额
*/
public static final Double MIN_WITHDRAW_AMOUNT = 100.00;
/**
* 最高提现金额
*/
public static final Double MAX_WITHDRAW_AMOUNT = 100000.00;
}
12.2 错误码定义 - ErrorCode.java
package com.canvas.chain.constant;
public class ErrorCode {
// 成功
public static final String SUCCESS = "200";
public static final String SUCCESS_MESSAGE = "操作成功";
// 客户端错误 (4xx)
public static final String BAD_REQUEST = "400";
public static final String BAD_REQUEST_MESSAGE = "请求参数错误";
public static final String UNAUTHORIZED = "401";
public static final String UNAUTHORIZED_MESSAGE = "未授权,请先登录";
public static final String FORBIDDEN = "403";
public static final String FORBIDDEN_MESSAGE = "禁止访问";
public static final String NOT_FOUND = "404";
public static final String NOT_FOUND_MESSAGE = "请求的资源不存在";
// 业务错误
public static final String USER_NOT_FOUND = "4001";
public static final String USER_NOT_FOUND_MESSAGE = "用户不存在";
public static final String USERNAME_ALREADY_EXISTS = "4002";
public static final String USERNAME_ALREADY_EXISTS_MESSAGE = "用户名已存在";
public static final String EMAIL_ALREADY_EXISTS = "4003";
public static final String EMAIL_ALREADY_EXISTS_MESSAGE = "邮箱已被注册";
public static final String PASSWORD_ERROR = "4004";
public static final String PASSWORD_ERROR_MESSAGE = "密码错误";
public static final String INVALID_TOKEN = "4005";
public static final String INVALID_TOKEN_MESSAGE = "无效的Token";
public static final String TOKEN_EXPIRED = "4006";
public static final String TOKEN_EXPIRED_MESSAGE = "Token已过期";
public static final String INSUFFICIENT_BALANCE = "4007";
public static final String INSUFFICIENT_BALANCE_MESSAGE = "余额不足";
// 服务器错误 (5xx)
public static final String INTERNAL_SERVER_ERROR = "500";
public static final String INTERNAL_SERVER_ERROR_MESSAGE = "服务器内部错误";
public static final String SERVICE_UNAVAILABLE = "503";
public static final String SERVICE_UNAVAILABLE_MESSAGE = "服务暂时不可用";
}
VibeCoding 导航:⬅️ 01-项目文档(Project Overview) | 02-CanvasChain 第1周 - 基础架构搭建详细设计文档 | ➡️ 01-计划
💬 评论